 /*******************************************************************************
  * Copyright (c) 2003, 2004 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Common Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/cpl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/
 package org.eclipse.core.internal.dependencies;

 import java.util.*;

 public class ElementSet {
     private DependencySystem system;
     private Object id;
     // # of iteration this element set was last visited
 transient private int visitedMark;
     // # of iteration this element set was last changed
 transient private int changedMark;
     // does this element set need to be recomputed? (the value is one of {UP_TO_DATE,SATISFACTION,SELECTION,RESOLUTION})
 transient private int needingUpdate;
     private int singletonsCount;
     private Collection requiring;
     private Collection required;
     private Map available;
     private Set satisfied;
     private Set selected;
     private Set resolved;
     private Map dependencyCounters;

     public ElementSet(Object id, DependencySystem system) {
         this.id = id;
         this.system = system;
         this.setNeedingUpdate(DependencySystem.SATISFACTION);
         this.available = new HashMap();
         this.satisfied = Collections.EMPTY_SET;
         this.selected = Collections.EMPTY_SET;
         this.resolved = Collections.EMPTY_SET;
         this.required = new LinkedList();
         this.requiring = new LinkedList();
         this.dependencyCounters = new HashMap();
     }

     public DependencySystem getSystem() {
         return system;
     }

     /** @return false if there is at least one version that does not allow concurrency. */
     public boolean allowsConcurrency() {
         return singletonsCount == 0;
     }

     void addElement(Element element) {
         if (this.available.containsKey(element.getVersionId()))
             return;
         this.setNeedingUpdate(DependencySystem.SATISFACTION);
         this.available.put(element.getVersionId(), element);
         Dependency[] dependencies = element.getDependencies();
         for (int i = 0; i < dependencies.length; i++)
             this.addRequired(dependencies[i].getRequiredObjectId());
         if (element.isSingleton())
             this.singletonsCount++;
         system.recordElementStatusChanged(element, ElementChange.ADDED);
     }

     void removeElement(Element element) {
         removeElement(element.getVersionId());
     }

     void removeElement(Object versionId) {
         Element toRemove = (Element) this.available.remove(versionId);
         if (toRemove == null)
             return;
         this.markNeedingUpdate(DependencySystem.SATISFACTION);
         Dependency[] dependencies = toRemove.getDependencies();
         for (int i = 0; i < dependencies.length; i++)
             removeRequired(dependencies[i].getRequiredObjectId());
         // if does not allow concurrency, decrement preventingConcurrencyCount
 if (toRemove.isSingleton())
             this.singletonsCount--;
         int change = ElementChange.REMOVED;
         // if (resolved.contains(toRemove))
 // change |= ElementChange.UNRESOLVED;
 system.recordElementStatusChanged(toRemove, change);
     }

     /**
      * Returns the unique id for this element set.
      */
     public Object getId() {
         return id;
     }

     /**
      * Is this a root element set?
      */
     public boolean isRoot() {
         return getRequired().isEmpty();
     }

     /**
      * Returns all elements available in this element set.
      */
     public Set getAvailable() {
         return new HashSet(available.values());
     }

     /**
      * Returns all elements sets required this element set.
      */

     public Collection getRequired() {
         return required;
     }

     /**
      * Returns all elements sets requiring this element set.
      */

     public Collection getRequiring() {
         return requiring;
     }

     /**
      * Returns all elements currently resolved in this element set.
      */

     public Set getResolved() {
         return resolved;
     }

     public void resolveDependency(Dependency dependency, Object resolvedVersionId) {
         dependency.resolve(resolvedVersionId, this.visitedMark);
     }

     public void setResolved(Set newResolved) {
         this.setNeedingUpdate(DependencySystem.UP_TO_DATE);

         //TODO: this may well be optimized...
 // maybe just a pre-requisite changed
 for (Iterator resolvedIter = this.resolved.iterator(); resolvedIter.hasNext();) {
             Element resolvedElement = (Element) resolvedIter.next();
             Dependency[] dependencies = resolvedElement.getDependencies();
             for (int i = 0; i < dependencies.length; i++)
                 if (dependencies[i].getChangedMark() == this.getVisitedMark()) {
                     system.recordElementStatusChanged(resolvedElement, ElementChange.LINKAGE_CHANGED);
                     break;
                 }
         }
         if (newResolved.equals(this.resolved))
             return;
         this.setChangedMark(visitedMark);
         Collection oldResolved = this.resolved;
         this.resolved = Collections.unmodifiableSet(newResolved);
         system.recordDependencyChanged(oldResolved, newResolved);
     }

     /**
      * Returns all elements currently selected in this element set.
      */

     public Set getSelected() {
         return selected;
     }

     public void setSelected(Set selected) {
         this.setNeedingUpdate(DependencySystem.RESOLUTION);
         if (selected.equals(this.selected))
             return;
         this.setChangedMark(visitedMark);
         this.selected = Collections.unmodifiableSet(selected);
     }

     /**
      * Returns all elements currently satisfied in this element set.
      */

     public Set getSatisfied() {
         return satisfied;
     }

     public void setSatisfied(Set satisfied) {
         this.setNeedingUpdate(DependencySystem.SELECTION);
         if (satisfied.equals(this.satisfied))
             return;
         this.setChangedMark(visitedMark);
         this.satisfied = Collections.unmodifiableSet(satisfied);
     }

     public String toString() {
         return this.id + ": " + available; //$NON-NLS-1$
 }

     public boolean equals(Object elementSet) {
         return ((ElementSet) elementSet).getId().equals(this.id);
     }

     public int hashCode() {
         return this.id.hashCode();
     }

     class DependencyCounter {
         int value;
     }

     private void addRequired(Object requiredId) {
         this.setNeedingUpdate(DependencySystem.SATISFACTION);
         ElementSet requiredNode = system.getElementSet(requiredId);
         DependencyCounter counter = (DependencyCounter) this.dependencyCounters.get(requiredId);
         if (counter == null) {
             this.dependencyCounters.put(requiredId, counter = new DependencyCounter());
             // links requiring and required element sets in both directions
 this.required.add(requiredNode);
             requiredNode.requiring.add(this);
             requiredNode.setNeedingUpdate(Math.min(requiredNode.getNeedingUpdate(), DependencySystem.SELECTION));
         }
         counter.value++;
     }

     private void removeRequired(Object requiredId) {
         ElementSet requiredNode = system.getElementSet(requiredId);
         DependencyCounter counter = (DependencyCounter) this.dependencyCounters.get(requiredId);
         if (counter == null) {
             if (system.inDebugMode())
                 System.err.println("Trying to remove non-existent dependency: " + this.id + " -> " + requiredId); //$NON-NLS-1$ //$NON-NLS-2$
 return;
         }
         counter.value--;
         if (counter.value == 0) {
             this.dependencyCounters.remove(requiredId);
             // removes links between requiring and required element sets
 this.required.remove(requiredNode);
             requiredNode.requiring.remove(this);
             requiredNode.setNeedingUpdate(DependencySystem.SELECTION);
         }
     }

     public void removeFromCycle() {
         Element[] availableElements = (Element[]) this.available.values().toArray(new Element[this.available.size()]);
         for (int i = 0; i < availableElements.length; i++) {
             removeElement(availableElements[i]);
             availableElements[i].removeFromCycle();
             addElement(availableElements[i]);
         }
     }

     int getRequiringCount() {
         return requiring.size();
     }

     int getElementCount() {
         return available.size();
     }

     int getVisitedMark() {
         return visitedMark;
     }

     void setVisitedMark(int mark) {
         this.visitedMark = mark;
     }

     int getChangedMark() {
         return changedMark;
     }

     private void setChangedMark(int mark) {
         this.changedMark = mark;
     }

     void markNeedingUpdate(int order) {
         setNeedingUpdate(order);
     }

     boolean isNeedingUpdate(int order) {
         return getNeedingUpdate() <= order;
     }

     Element getElement(Object versionId) {
         return (Element) this.available.get(versionId);
     }

     /**
      * Assumes resolved system - returns all elements whose dependencies
      * were resolved to point to the specified element.
      */

     public Collection getRequiringElements(Object versionId) {
         Collection result = new LinkedList();
         for (Iterator requiringSetsIter = requiring.iterator(); requiringSetsIter.hasNext();) {
             ElementSet requiringSet = (ElementSet) requiringSetsIter.next();
             for (Iterator iter = requiringSet.getResolved().iterator(); iter.hasNext();) {
                 Element element = (Element) iter.next();
                 Dependency requisite = element.getDependency(this.id);
                 if (requisite != null && versionId.equals(requisite.getResolvedVersionId()))
                     result.add(element);
             }
         }
         return result;
     }

     public void unresolve(Element element, int mark) {
         setVisitedMark(mark);
         if (!resolved.contains(element))
             return;
         Set newResolved = new HashSet(resolved);
         newResolved.remove(element);
         resolved = Collections.unmodifiableSet(newResolved);

         Dependency[] dependencies = element.getDependencies();
         // unresolved dependencies
 for (int i = 0; i < dependencies.length; i++)
             resolveDependency(dependencies[i], null);
         setChangedMark(mark);
         setNeedingUpdate(DependencySystem.SATISFACTION);
     }

     private void setNeedingUpdate(int needingUpdate) {
         this.needingUpdate = needingUpdate;
     }

     private int getNeedingUpdate() {
         return needingUpdate;
     }

 }
